package com.supercommons.swagger;

import com.google.common.collect.Lists;
import org.apache.naming.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;

@Import({Swagger2Configuration.class})
public class SwaggerAutoConfiguration {

    private BeanFactory beanFactory;
    // 请求头 定义变量  赋值
    private static final String AUTH_KEY = "Authorization";

    /**
     * 把对象 SwaggerProperties 加载到bean容器里
     */
    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "spring.swagger.enabled", havingValue = "true")
    public List<Docket> createRestApi(SwaggerProperties swaggerProperties) {
        List<Docket> docketList = Lists.newArrayList();
        // 配置的bean仓
        ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;

        // 如果没有分组
        if (0 == swaggerProperties.getDocket().size()) {
            final Docket docket = createDocket(swaggerProperties);
            configurableBeanFactory.registerSingleton("defaultDocket", docket);
            docketList.add(docket);
        } else {// 分组
            createDocketList(swaggerProperties, docketList, configurableBeanFactory);
        }
        return docketList;
    }


    /**
     * 没有分组 创建 Docket对象
     *
     * @param swaggerProperties swagger配置
     * @return Docket
     */
    private Docket createDocket(final SwaggerProperties swaggerProperties) {
        // 接口信息
        ApiInfo apiInfo = new ApiInfoBuilder().title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription()).version(swaggerProperties.getVersion())
                .license(swaggerProperties.getLicense()).licenseUrl(swaggerProperties.getLicenseUrl())
                .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(),
                        swaggerProperties.getContact().getEmail()))
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl()).build();

        // base-path处理
        // 当没有配置任何path的时候，解析/**
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add("/**");
        }
        List<Predicate<String>> basePath = Lists.newArrayList();
        for (String path : swaggerProperties.getBasePath()) {
            basePath.add(PathSelectors.ant(path));
        }

        // exclude-path处理
        List<Predicate<String>> excludePath = Lists.newArrayList();
        for (String path : swaggerProperties.getExcludePath()) {
            excludePath.add(PathSelectors.ant(path));
        }

        return new Docket(DocumentationType.SWAGGER_2)
                // 端口号
                .host(swaggerProperties.getHost())
                // 接口信息
                .apiInfo(apiInfo)
                // 全局操作参数
                .globalOperationParameters(buildGlobalOperationParametersFromSwaggerProperties(
                        swaggerProperties.getGlobalOperationParameters()))
                // 配置扫描  已 select() 开头  已 build()结束
                .select()
                // basePackage 自动路径扫描
                // any 全部扫描
                // none 全部不扫描
                // withMethodAnnotation 扫描方法上有注解class 例如: withMethodAnnotation(GetMapping.class)
                // withClassAnnotation  扫描类 上有注解class  例如: withClassAnnotation(RequestMapping.class)
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                // 过滤 指定路径 不扫描
                .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
                .build()
                // 安全方案
                .securitySchemes(securitySchemes())
                // 安全上下文
                .securityContexts(securityContexts());
    }

    /**
     * 分组  创建 List<Docket> 对象
     *
     * @param swaggerProperties swagger配置
     * @return Docket
     */
    private List<Docket> createDocketList(final SwaggerProperties swaggerProperties, List<Docket> docketList, ConfigurableBeanFactory configurableBeanFactory) {
        // 分组创建
        for (String groupName : swaggerProperties.getDocket().keySet()) {
            // 获取 分组文档
            SwaggerProperties.DocketInfo docketInfo = swaggerProperties.getDocket().get(groupName);
            // 定义 接口信息
            ApiInfo apiInfo = new ApiInfoBuilder()
                    .title(docketInfo.getTitle().isEmpty() ? swaggerProperties.getTitle() : docketInfo.getTitle())
                    .description(docketInfo.getDescription().isEmpty() ? swaggerProperties.getDescription()
                            : docketInfo.getDescription())
                    .version(docketInfo.getVersion().isEmpty() ? swaggerProperties.getVersion()
                            : docketInfo.getVersion())
                    .license(docketInfo.getLicense().isEmpty() ? swaggerProperties.getLicense()
                            : docketInfo.getLicense())
                    .licenseUrl(docketInfo.getLicenseUrl().isEmpty() ? swaggerProperties.getLicenseUrl()
                            : docketInfo.getLicenseUrl())
                    .contact(new Contact(
                            docketInfo.getContact().getName().isEmpty() ? swaggerProperties.getContact().getName()
                                    : docketInfo.getContact().getName(),
                            docketInfo.getContact().getUrl().isEmpty() ? swaggerProperties.getContact().getUrl()
                                    : docketInfo.getContact().getUrl(),
                            docketInfo.getContact().getEmail().isEmpty() ? swaggerProperties.getContact().getEmail()
                                    : docketInfo.getContact().getEmail()))
                    .termsOfServiceUrl(docketInfo.getTermsOfServiceUrl().isEmpty()
                            ? swaggerProperties.getTermsOfServiceUrl() : docketInfo.getTermsOfServiceUrl())
                    .build();

            // base-path处理
            // 当没有配置任何path的时候，解析/**
            if (docketInfo.getBasePath().isEmpty()) {
                docketInfo.getBasePath().add("/**");
            }
            List<Predicate<String>> basePath = new ArrayList<>(docketInfo.getBasePath().size());
            for (String path : docketInfo.getBasePath()) {
                basePath.add(PathSelectors.ant(path));
            }

            // exclude-path处理
            List<Predicate<String>> excludePath = new ArrayList<>(docketInfo.getExcludePath().size());
            for (String path : docketInfo.getExcludePath()) {
                excludePath.add(PathSelectors.ant(path));
            }

            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    // 端口
                    .host(swaggerProperties.getHost())
                    // 接口信息
                    .apiInfo(apiInfo)
                    // 全局操作参数
                    .globalOperationParameters(assemblyGlobalOperationParameters(swaggerProperties.getGlobalOperationParameters(), docketInfo.getGlobalOperationParameters()))
                    // 分组名称
                    .groupName(groupName)
                    // 配置扫描  已 select() 开头  已 build()结束
                    .select()
                    // basePackage 自动路径扫描
                    // any 全部扫描
                    // none 全部不扫描
                    // withMethodAnnotation 扫描方法上有注解class 例如: withMethodAnnotation(GetMapping.class)
                    // withClassAnnotation  扫描类 上有注解class  例如: withClassAnnotation(RequestMapping.class)
                    .apis(RequestHandlerSelectors.basePackage(docketInfo.getBasePackage()))
                    // 过滤 指定路径 不扫描
                    .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
                    .build()
                    // 安全方案
                    .securitySchemes(securitySchemes())
                    // 安全上下文
                    .securityContexts(securityContexts());
            // 存放配置Bean仓下
            configurableBeanFactory.registerSingleton(groupName, docket);
            docketList.add(docket);
        }
        return docketList;
    }

    /**
     * 局部参数按照name覆盖局部参数
     *
     * @param globalOperationParameters
     * @param docketOperationParameters
     * @return
     */
    private List<Parameter> assemblyGlobalOperationParameters(
            List<SwaggerProperties.GlobalOperationParameter> globalOperationParameters,
            List<SwaggerProperties.GlobalOperationParameter> docketOperationParameters) {
        if (Objects.isNull(docketOperationParameters) || docketOperationParameters.isEmpty()) {
            return buildGlobalOperationParametersFromSwaggerProperties(globalOperationParameters);
        }
        Set<String> docketNames = docketOperationParameters.stream()
                .map(SwaggerProperties.GlobalOperationParameter::getName).collect(Collectors.toSet());
        List<SwaggerProperties.GlobalOperationParameter> resultOperationParameters = Lists.newArrayList();
        if (Objects.nonNull(globalOperationParameters)) {
            for (SwaggerProperties.GlobalOperationParameter parameter : globalOperationParameters) {
                if (!docketNames.contains(parameter.getName())) {
                    resultOperationParameters.add(parameter);
                }
            }
        }
        resultOperationParameters.addAll(docketOperationParameters);
        return buildGlobalOperationParametersFromSwaggerProperties(resultOperationParameters);
    }

    private List<Parameter> buildGlobalOperationParametersFromSwaggerProperties(
            List<SwaggerProperties.GlobalOperationParameter> globalOperationParameters) {
        List<Parameter> parameters = Lists.newArrayList();

        if (Objects.isNull(globalOperationParameters)) {
            return parameters;
        }
        for (SwaggerProperties.GlobalOperationParameter globalOperationParameter : globalOperationParameters) {
            parameters.add(new ParameterBuilder().name(globalOperationParameter.getName())
                    .description(globalOperationParameter.getDescription())
                    .modelRef(new ModelRef(globalOperationParameter.getModelRef()))
                    .parameterType(globalOperationParameter.getParameterType())
                    .required(Boolean.parseBoolean(globalOperationParameter.getRequired())).build());
        }
        return parameters;
    }

    /**
     * 安全方案
     * */
    private List<ApiKey> securitySchemes() {
        List<ApiKey> apiKeys = Lists.newArrayList();
        ApiKey apiKey = new ApiKey(AUTH_KEY, AUTH_KEY, "header");
        apiKeys.add(apiKey);
        return apiKeys;
    }
    /**
     * 安全上下文
     * */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = Lists.newArrayList();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
                        .build());
        return securityContexts;
    }
    // 默认值
    List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = Lists.newArrayList();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }


}
